#include "LinkedList.h"
#include <stdlib.h>
#include <stdio.h>

MyLinkedList *myLinkedListNew() {
    MyLinkedList *myLinkedList = malloc(sizeof(MyLinkedList));
    checkAddress(myLinkedList);
    myLinkedList->head = 0;
    myLinkedList->tail = 0;
    return myLinkedList;
}

void checkAddress(void *p) {
    if (p == NULL) {
        printf("Unable to allocate memory.\n");
        exit(EXIT_FAILURE);
    }
}

void myLinkedListDestroy(MyLinkedList *myLinkedList) {
    struct ListNode *tmp = myLinkedList->head;
    struct ListNode *nextTmp;
    while (tmp != 0) {
        nextTmp = tmp->next;
        free(tmp);
        tmp = nextTmp;
    }
    free(myLinkedList);
}

int myLinkedListSize(MyLinkedList *myLinkedList) {
    int size = 0;
    struct ListNode *tmp = myLinkedList->head;

    // Pointer pointing to NULL
    while(tmp != 0) {

        size++;
        tmp = tmp->next;
    }
    return size;
}

bool myLinkedListIsEmpty(MyLinkedList *myLinkedList) {
    return myLinkedList->head == 0;
}

void myLinkedListPushFront(MyLinkedList *myLinkedList, int value) {
    struct ListNode *node = malloc(sizeof(struct ListNode));
    node->data = value;
    node->next = myLinkedList->head;
    myLinkedList->head = node;
    // If the old linked list is empty
    if (myLinkedList->tail == 0) {
        myLinkedList->tail = node;
    }
}

void myLinkedListPrintDebug(MyLinkedList *myLinkedList) {
    struct ListNode *current = myLinkedList->head;

    printf("head: %p\n", myLinkedList->head);
    printf("tail: %p\n", myLinkedList->tail);
    printf("path: ");

    while (current != 0) {
        printf("%d -> ", current->data);
        current = current->next;
    }

    printf("\n\n");
}

int myLinkedListFront(MyLinkedList *myLinkedList) {
    if (myLinkedList->head == 0) {
        printf("Cannot get front of empty list\n");
        exit(EXIT_FAILURE);
    }
    return myLinkedList->head->data;
}

int myLinkedListBack(MyLinkedList *myLinkedList) {
    if (myLinkedList->tail == 0) {
        printf("Cannot get back of empty list\n");
        exit(EXIT_FAILURE);
    }
    return myLinkedList->tail->data;
}

void myLinkedListPopFront(MyLinkedList *myLinkedList) {
    if (myLinkedList->head == 0) {
        printf("Cannot pop front of empty list\n");
        exit(EXIT_FAILURE);
    }
    struct ListNode *tmp = myLinkedList->head;
    myLinkedList->head = myLinkedList->head->next;
    if (myLinkedList->tail == tmp) {
        myLinkedList->tail = myLinkedList->head;
    }
    free(tmp);
}

void myLinkedListPushBack(MyLinkedList *myLinkedList, int data) {
    struct ListNode *newNode = malloc(sizeof(struct ListNode));
    checkAddress(newNode);
    newNode->data = data;
    newNode->next = 0;
    if (myLinkedList->head == 0) {
        myLinkedList->head = newNode;
        myLinkedList->tail = newNode;
    } else {
        myLinkedList->tail->next = newNode;
        myLinkedList->tail = newNode;
    }
}

void myLinkedListPopBack(MyLinkedList *myLinkedList) {
    struct ListNode *tmp = myLinkedList->head;
    if (myLinkedList->head == 0) {
        printf("Cannot pop the last element of empty list.");
        exit(EXIT_FAILURE);
    }
    if (myLinkedList->head->next == 0) {
        free(myLinkedList->head);
        myLinkedList->head = 0;
        myLinkedList->tail = 0;
    } else {
        while (tmp->next->next != 0) {
            tmp = tmp->next;
        }
        free(myLinkedList->tail);
        myLinkedList->tail = tmp;
        tmp->next = 0;
    }
}

void myLinkedListInsert(MyLinkedList *myLinkedList, int index, int data) {
    struct ListNode *currNode = myLinkedList->head;
    struct ListNode *preNode = myLinkedList->head;
    struct ListNode *newNode = malloc(sizeof(struct ListNode));
    newNode->data = data;
    int currIndex = 0;

    if (myLinkedList->head == 0) {
        if (index != 0) {
            printf("Index out of bound.");
            exit(EXIT_FAILURE);
        }
        newNode->next = 0;
        myLinkedList->head = newNode;
        myLinkedList->tail = newNode;
    } else {
        while (currIndex < index && currNode->next != 0) {
            currIndex++;
            preNode = currNode;
            currNode = currNode->next;
        }
        if (currIndex < index) {
            printf("Index out of bound.");
            exit(EXIT_FAILURE);
        }
        if (index == 0) {
            newNode->next = myLinkedList->head;
            myLinkedList->head = newNode;
        } else {
            newNode->next = currNode;
            preNode->next = newNode;
        }
    }
}

int myLinkedListValueAt(MyLinkedList *myLinkedList, int index) {
    if (myLinkedList->head == 0) {
        printf("List is empty.");
        exit(EXIT_FAILURE);
    }
    struct ListNode * currNode = myLinkedList->head;
    int currIndex = 0;
    while (currIndex < index && currNode->next != 0) {
        currIndex++;
        currNode = currNode->next;
    }
    if (currIndex < index) {
        printf("Index out of bounds.");
        exit(EXIT_FAILURE);
    }
    return currNode->data;
}

void myLinkedListErase(MyLinkedList *myLinkedList, int index) {
    if (myLinkedList->head == 0) {
        printf("List is empty.");
        exit(EXIT_FAILURE);
    }
    if (myLinkedList->head->next == 0) {
        if (index != 0) {
            printf("Index out of bounds.");
            exit(EXIT_FAILURE);
        }
        free(myLinkedList->head);
        myLinkedList->head = 0;
        myLinkedList->tail = 0;
    } else{
        struct ListNode *currNode = myLinkedList->head;
        struct ListNode *preNode = myLinkedList->head;
        int currIndex = 0;
        while (currIndex < index && currNode->next != 0) {
            currIndex++;
            preNode = currNode;
            currNode = currNode->next;
        }
        if (currIndex < index) {
            printf("Index out of bounds.");
            exit(EXIT_FAILURE);
        }
        if (currIndex == 0) {
            myLinkedList->head = currNode->next;
        } else if (currNode == myLinkedList->tail) {
            preNode->next = 0;
            myLinkedList->tail = preNode;
        } else {
            preNode->next = currNode->next;
        }
        free(currNode);
    }

}

int myLinkedListNFromEnd(MyLinkedList *myLinkedList, int n) {
    if (myLinkedList->head == 0) {
        printf("List is empty.");
        exit(EXIT_FAILURE);
    }
    struct ListNode *front = myLinkedList->head;
    struct ListNode *back = myLinkedList->head;
    for (int i = 0; i < n-1; i++) {
        front = front->next;
        if (front == 0) {
            printf("Index out of bounds.");
            exit(EXIT_FAILURE);
        }
    }
    while (front->next != 0) {
        front = front->next;
        back = back->next;
    }
    return back->data;
}

void myLinkedListReverse(MyLinkedList *myLinkedList) {
    struct ListNode *prev = NULL;
    struct ListNode *curr = myLinkedList->head;
    struct ListNode *next;
    while (curr) {
        next = curr->next;
        curr->next = prev;
        prev = curr;
        curr = next;
    }
    myLinkedList->tail = myLinkedList->head;
    myLinkedList->head = prev;
}

void myLinkedListRemove(MyLinkedList *myLinkedList, int data) {
    if (myLinkedList->head == 0) {
        printf("List is empty.");
        exit(EXIT_FAILURE);
    }
    struct ListNode *pre = myLinkedList->head;
    struct ListNode *curr = myLinkedList->head;

    while (curr->next != 0) {
        if (curr->data == data) {
            if (curr->next == 0) {
                free(curr);
                myLinkedList->head = 0;
                myLinkedList->tail = 0;
            } else {
                if (curr == myLinkedList->head) {
                    myLinkedList->head = curr->next;
                    free(curr);
                } else if (curr == myLinkedList->tail) {
                    free(curr);
                    pre->next = 0;
                    myLinkedList->tail = pre;
                } else {
                    pre->next = curr->next;
                    free(curr);
                }
            }
            break;
        } else {
            pre = curr;
            curr = curr->next;
        }
    }
}
